﻿using System;
using System.Collections.Generic;
using System.Text;

namespace AndersLiu.Reflector.Program
{
    /// <summary>
    /// Provides functions to save the view history and go back/forward.
    /// </summary>
    /// <typeparam name="THistoryPoint">
    /// Type of object that saves status data at a history point.
    /// </typeparam>
    class HistoryList<THistoryPoint> where THistoryPoint : class
    {
        /// <summary>
        /// Get a bool value, indicates whether user can go back.
        /// </summary>
        public bool CanGoBack
        {
            get { return GetCanGoBack(1); }
        }

        /// <summary>
        /// Get a bool value, indicates whether user can go forward.
        /// </summary>
        public bool CanGoForward
        {
            get { return GetCanGoForward(1); }
        }

        /// <summary>
        /// Get the steps that user can go back.
        /// </summary>
        /// <remarks>
        /// <para>If user cannot go back, returns an empty array
        /// instead a null valu.</para>
        /// </remarks>
        public IEnumerable<THistoryPoint> BackSteps
        {
            get
            {
                for (var i = GetMaxGoBackSteps() - 1; i >= 0; i--)
                    yield return _historyList[i];
            }
        }

        /// <summary>
        /// Get the steps that user can go forward.
        /// </summary>
        /// <remarks>
        /// <para>If user cannot go forward, returns an empty array
        /// instead a null valu.</para>
        /// </remarks>
        public IEnumerable<THistoryPoint> ForwardSteps
        {
            get
            {
                for (var i = _currentPoint + 1; i < _historyList.Count; i++)
                    yield return _historyList[i];
            }
        }

        /// <summary>
        /// Get the current 'history point'.
        /// </summary>
        public THistoryPoint Current
        {
            get { return _currentPoint >= 0 ? _historyList[_currentPoint] : null; }
        }

        /// <summary>
        /// Go back one step.
        /// </summary>
        /// <param name="navigationSource">Which object causes the navigation.</param>
        public void GoBack(object navigationSource)
        {
            GoBack(navigationSource, 1);
        }

        /// <summary>
        /// Go back specified steps.
        /// </summary>
        /// <param name="navigationSource">Which object causes the navigation.</param>
        /// <param name="steps">Steps to go back.</param>
        public void GoBack(object navigationSource, int steps)
        {
            if (!GetCanGoBack(steps))
                return;

            var curPnt = this.Current;
            _currentPoint -= steps;

            OnCurrentChanged(new HistoryChangedEventArgs<THistoryPoint>(curPnt, navigationSource));
        }

        /// <summary>
        /// Go forward one step.
        /// </summary>
        /// <param name="navigationSource">Which object causes the navigation.</param>
        public void GoForward(object navigationSource)
        {
            GoForward(navigationSource, 1);
        }

        /// <summary>
        /// Go forward speicified steps.
        /// </summary>
        /// <param name="steps">Steps to go forward.</param>
        /// <param name="navigationSource">Which object causes the navigation.</param>
        public void GoForward(object navigationSource, int steps)
        {
            if (!GetCanGoForward(steps))
                return;

            var curPnt = this.Current;
            _currentPoint += steps;

            OnCurrentChanged(new HistoryChangedEventArgs<THistoryPoint>(curPnt, navigationSource));
        }

        /// <summary>
        /// Go to a new history point.
        /// </summary>
        /// <param name="point">History point to go.</param>
        public void GoTo(object navigationSource, THistoryPoint point)
        {
            if (point == this.Current)
                return;

            var curPnt = this.Current;

            if (_currentPoint >= _historyList.Count - 1)
            {
                // --- SECTION START ---
                // These lines of codes control the limit (max length) of the history list.
                // If you want implement a unlimited history list, please comment out these lines.
                if (_historyList.Count >= 50)
                {
                    _historyList.RemoveAt(0);  // Remove the oldest point.
                    _currentPoint--;
                }
                // --- SECTION END ---

                _historyList.Add(point);
            }
            else
            {
                _historyList[_currentPoint + 1] = point;
                _historyList.RemoveRange(
                    _currentPoint + 2,
                    _historyList.Count - _currentPoint - 2);
            }
            _currentPoint++;

            OnCurrentChanged(new HistoryChangedEventArgs<THistoryPoint>(curPnt, navigationSource));
        }

        /// <summary>
        /// Clear the history list.
        /// </summary>
        /// <param name="navigationSource"></param>
        public void Clear(object navigationSource)
        {
            _currentPoint = -1;
            _historyList.Clear();
            OnCurrentChanged(new HistoryChangedEventArgs<THistoryPoint>(null, navigationSource));
        }

        /// <summary>
        /// Fired when user navigated to another history point.
        /// </summary>
        public event EventHandler<HistoryChangedEventArgs<THistoryPoint>> Changed;

        /// <summary>
        /// Fire the <see cref="CurrentChanged"/> event.
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnCurrentChanged(HistoryChangedEventArgs<THistoryPoint> e)
        {
            if (this.Changed != null)
                this.Changed(this, e);
        }

        /// <summary>
        /// Saves all the navigation history points.
        /// </summary>
        private List<THistoryPoint> _historyList = new List<THistoryPoint>();

        /// <summary>
        /// Saves the index of the current history point.
        /// </summary>
        private int _currentPoint = -1;

        /// <summary>
        /// Get the max steps that user can go back.
        /// </summary>
        /// <returns></returns>
        private int GetMaxGoBackSteps()
        {
            return _currentPoint >= 0 ? _currentPoint : 0;
        }

        /// <summary>
        /// Get the max steps that user can go forward.
        /// </summary>
        /// <returns></returns>
        private int GetMaxGoForwardSteps()
        {
            var steps = _historyList.Count - 1 - _currentPoint;
            return steps >= 0 ? steps : 0;
        }

        /// <summary>
        /// Get a value, indicates whether user can go back specified steps.
        /// </summary>
        /// <param name="steps"></param>
        /// <returns></returns>
        private bool GetCanGoBack(int steps)
        {
            if (steps <= 0)
                throw new ArgumentException();

            return steps <= GetMaxGoBackSteps();
        }

        /// <summary>
        /// Get a value, indicates whether user can go forward specified steps.
        /// </summary>
        /// <param name="steps"></param>
        /// <returns></returns>
        private bool GetCanGoForward(int steps)
        {
            if (steps <= 0)
                throw new ArgumentException();

            return steps <= GetMaxGoForwardSteps();
        }
    }
}
